#include "image.h"
#include "../math.h"

eImage::eImage (eImageFileType type)
{
  m_type = type;
}

eImage::eImage ( int               width,
                 int               height,
                 eImagePixelFormat format,
                 int               bpp,
                 byte*             data,
                 bool              owns_data )
{
  Assert (data != NULL, _T ("Invalid data passed to eImage's constructor!"));

  if (data == NULL) {
    m_width     = 0;
    m_height    = 0;
    m_bpp       = 0;
    m_channels  = 0;
    m_data      = NULL;
    m_owns_data = false;

    return;
  }

  m_type      = eImageFileTypes::RAW;
  m_format    = format;
  m_width     = width;
  m_height    = height;
  m_bpp       = bpp;
  m_data      = data;
  m_owns_data = owns_data;

  switch (format)
  {
    case eImagePixelFormats::RGB:
    case eImagePixelFormats::BGR:
      m_channels = 3;
      break;
    case eImagePixelFormats::RGBA:
      m_channels = 4;
      break;
    default:
      Assert (false, _T ("Invalid pixel format"));
      m_channels = 0;
      break;
  }
}

eImage::~eImage (void)
{
  if (m_owns_data) {
    if (m_data != NULL) {
      delete [] m_data;
      m_data = NULL;
    }
  }
}


ePalettedImage::ePalettedImage ( int               width,
                                 int               height,
                                 eImagePixelFormat format,
                                 int               bpp,
                                 byte*             data,
                                 const byte*       palette,
                                 bool              owns_data ) :
  eImage (width, height, format, bpp, data, owns_data)
{
  m_palette = NULL;

  SetPalette (palette);
}

ePalettedImage::~ePalettedImage (void)
{
  if (m_palette != NULL) {
    delete [] m_palette;
    m_palette = NULL;
  }
}


#define max(a,b)            (((a) > (b)) ? (a) : (b))

eImage*
ePalettedImage::Convert (eImagePixelFormat format, int bpp, byte** buf)
{
  switch (Format ()) {
    case eImagePixelFormats::RGB:
    case eImagePixelFormats::BGR:
    {
      byte*   new_data = new byte [m_width * m_height * bpp];
      eImage* new_img  = new eImage (m_width, m_height, format, bpp, new_data);

      byte* d = new_data;

      for (int i = 0; i < m_width * m_height; i++) {
        byte color = m_data [i];

        *d++ = max (0, (signed)m_palette [color * 3]);
        *d++ = max (0, (signed)m_palette [color * 3 + 1]);
        *d++ = max (0, (signed)m_palette [color * 3 + 2]);
      }

      if (buf != NULL) {
        *buf        = new_data;
        m_owns_data = false;
      }

////      new_img->GenerateDOT3 ();

      return new_img;
    } break;
    case eImagePixelFormats::RGBA:
    {
      byte*   new_data = new byte [m_width * m_height * 4];
      eImage* new_img  = new eImage (m_width, m_height, format, bpp, new_data);

      byte* d = new_data;

      for (int i = 0; i < m_width * m_height; i++) {
        byte color = m_data [i];

        *d++ = max (0, (signed)m_palette [color * 4]);
        *d++ = max (0, (signed)m_palette [color * 4 + 1]);
        *d++ = max (0, (signed)m_palette [color * 4 + 2]);
        *d++ = max (0, (signed)m_palette [color * 4 + 3]);
      }

      if (buf != NULL) {
        *buf        = new_data;
        m_owns_data = false;
      }

      return new_img;
    } break;
  };

  Assert (false, _T ("ePalettedImage::Convert (...) is unsupported for this image!"));

  return NULL;
}

eImage*
eImage::Convert (eImagePixelFormat format, int bpp, byte** buf)
{
  if (buf != NULL)
    m_owns_data = false;

  // Swap RGB and BGR.
  if (m_bpp == 3 && bpp == 3) {
    if ((Format () == eImagePixelFormats::RGB &&
         format    == eImagePixelFormats::BGR) ||
        (Format () == eImagePixelFormats::BGR &&
         format    == eImagePixelFormats::RGB)) {
            byte* d;
      const byte* s;

      for (int i = 0; i < (m_width * m_height); i++) {
        s = &((byte *)m_data) [i * 3];

        const byte red   = *s;
        const byte blue  = *(s + 2);

        d = &((byte *)m_data) [i * 3];

        *d++ = blue;
        d++;
        *d   = red;
      }
    }
  }

  // Convert RGB to RGBA
  if (m_bpp == 3 && bpp == 4) {
    byte*   d   = new byte [m_width * m_height * 4];
    eImage* img = new eImage (m_width, m_height, eImagePixelFormats::RGBA, 4, d, true);

    const byte* s;

    for (int i = 0; i < (m_width * m_height); i++) {
      s = &((byte *)m_data) [i * 3];

      const byte red   = *s;
      const byte green = *(s + 1);
      const byte blue  = *(s + 2);

      d = &img->Data () [i * 4];

      *d++ = red;
      *d++ = green;
      *d++ = blue;

      if (red   == 0x00 &&
          green == 0x00 &&
          blue  == 0x00) {
        *d++ = 0x00;
      } else if (  red == 0xff &&
                 green == 0x00 &&
                 blue  == 0xff) {
        *d++ = 0x00; // Pink alpha-mask
      } else {
        *d++ = 0xff;
      }
    }

    return img;
  }

  ////Assert (false, _T ("eImage::Convert (...) is unsupported for this image!"));

  return NULL;
}


void
ePalettedImage::SetPalette (const byte* palette)
{
  Assert (palette != NULL,
    _T ("Invalid palette passed to SetPalette (...)!"));

  if (palette == NULL)
    return;

  if (m_palette != NULL)
    delete [] m_palette;

  m_palette = new byte [(1 << (m_bpp * 8)) * m_channels];

  memcpy (m_palette, palette, sizeof (byte) * (1 << (m_bpp * 8)) * m_channels);
}



#include "../file.h"

eImage*
LoadImage (eFile& image_file)
{
  eImage* img = NULL;

  if (image_file.size ()) {
    printf ("Cool");

    img = new Epsilon::Image::TGA (image_file);
  }

  return img;
}


#include "../file.h"

void
Epsilon::Image::TGA::set_file (eFile& file)
{
  file_ = &file;
}

#include "tga.h"

bool
Epsilon::Image::TGA::Load (void)
{
  TGAFILE tga;
  e3dLoadTGA (*file_, &tga);

  m_width     = tga.imageWidth;
  m_height    = tga.imageHeight;
  m_bpp       = tga.bitCount / 8;
  m_channels  = tga.numChannels;
  m_data      = tga.imageData;
  m_owns_data = true;

  if (m_channels > 3)
    m_format  = eImagePixelFormats::RGBA;
  else
    m_format  = eImagePixelFormats::RGB;

  return true;
}
















float AverageTexel (const byte* texel)
{
  float fAccum = 0.0f;

  fAccum += *texel++;
  fAccum += *texel++;
  fAccum += *texel;

  return (fAccum / 3.0f);
}

inline static byte PackFloatInByte (float in) {
  return (byte) ((in + 1.0f) / 2.0f * 255.0f);
}

eImage*
eImage::GenerateDOT3 (void)
{
  int    x,  y;
  float dX, dY,
        nX, nY,
        nZ, oolen;

  const byte* pixel     = NULL;
        byte* pixel_out = NULL;

  eImage* image_out;
  byte*   new_data = new byte [m_width * m_height * m_bpp];

  const eImagePixelFormat format = eImagePixelFormats::RGB;

  image_out =
    new eImage (m_width, m_height, format, m_bpp, new_data, true);

  //image_out->Allocate (m_width, m_height, m_bpp);

  for (y = 0; y < m_height; y++) {
    for (x = 0; x < m_width; x++) {
      // Do Y Sobel filter
      pixel = Pixel ((x-1+m_width) % m_width, (y+1) % m_height);
      dY  = (AverageTexel (pixel)) / 255.0f * -1.0f;

      pixel = Pixel (  x   % m_width, (y+1) % m_height);
      dY += (AverageTexel (pixel)) / 255.0f * -2.0f;

      pixel = Pixel ((x+1) % m_width, (y+1) % m_height);
      dY += (AverageTexel (pixel)) / 255.0f * -1.0f;

      pixel = Pixel ((x-1+m_width) % m_width, (y-1+m_height) % m_height);
      dY += (AverageTexel (pixel)) / 255.0f *  1.0f;

      pixel = Pixel (  x   % m_width, (y-1+m_height) % m_height);
      dY += (AverageTexel (pixel)) / 255.0f *  2.0f;

      pixel = Pixel ((x+1) % m_width, (y-1+m_height) % m_height);
      dY += (AverageTexel (pixel)) / 255.0f *  1.0f;

      // Do X Sobel filter
      pixel = Pixel ((x-1+m_width) % m_width, (y-1+m_height) % m_height);
      dX  = (AverageTexel (pixel)) / 255.0f * -1.0f;

      pixel = Pixel ((x-1+m_width) % m_width,   y   % m_height);
      dX += (AverageTexel (pixel)) / 255.0f * -2.0f;

      pixel = Pixel ((x-1+m_width) % m_width, (y+1) % m_height);
      dX += (AverageTexel (pixel)) / 255.0f * -1.0f;

      pixel = Pixel ((x+1) % m_width, (y-1+m_height) % m_height);
      dX += (AverageTexel (pixel)) / 255.0f *  1.0f;

      pixel = Pixel ((x+1) % m_width,   y   % m_height);
      dX += (AverageTexel (pixel)) / 255.0f *  2.0f;

      pixel = Pixel ((x+1) % m_width, (y+1) % m_height);
      dX += (AverageTexel (pixel)) / 255.0f *  1.0f;


      // Cross Product of components of gradient reduces to
      nX = -dX;
      nY = -dY;
      nZ =   1;

      // Normalize
      oolen = 1.0f / (eMath<float>::Sqrt (nX * nX +
                                          nY * nY +
                                          nZ * nZ));
      nX *= oolen;
      nY *= oolen;
      nZ *= oolen;

      pixel_out = image_out->Pixel (x, y);

      pixel_out [0] = (byte) PackFloatInByte (nX);
      pixel_out [1] = (byte) PackFloatInByte (nY);
      pixel_out [2] = (byte) PackFloatInByte (nZ);

      if (image_out->Channels () == 4)
        pixel_out [3] = 32;
    }
  }

  memcpy (m_data, image_out->Data (), m_width * m_height * m_bpp);

  delete image_out;

  return NULL;

  //return image_out;
}
